# Licensed under a 3-clause BSD style license - see LICENSE.rst


# Standard library
import re
import textwrap
import warnings
from datetime import datetime
from typing import Final
from urllib.request import Request, urlopen

# Third-party
from astropy import time as atime
from astropy.utils.console import _color_text, color_print

from .funcs import get_sun

__all__: Final[list[str]] = []


class HumanError(ValueError):
    pass


class CelestialError(ValueError):
    pass


def get_sign(dt):
    """ """
    if (int(dt.month) == 12 and int(dt.day) >= 22) or (
        int(dt.month) == 1 and int(dt.day) <= 19
    ):
        zodiac_sign = "capricorn"
    elif (int(dt.month) == 1 and int(dt.day) >= 20) or (
        int(dt.month) == 2 and int(dt.day) <= 17
    ):
        zodiac_sign = "aquarius"
    elif (int(dt.month) == 2 and int(dt.day) >= 18) or (
        int(dt.month) == 3 and int(dt.day) <= 19
    ):
        zodiac_sign = "pisces"
    elif (int(dt.month) == 3 and int(dt.day) >= 20) or (
        int(dt.month) == 4 and int(dt.day) <= 19
    ):
        zodiac_sign = "aries"
    elif (int(dt.month) == 4 and int(dt.day) >= 20) or (
        int(dt.month) == 5 and int(dt.day) <= 20
    ):
        zodiac_sign = "taurus"
    elif (int(dt.month) == 5 and int(dt.day) >= 21) or (
        int(dt.month) == 6 and int(dt.day) <= 20
    ):
        zodiac_sign = "gemini"
    elif (int(dt.month) == 6 and int(dt.day) >= 21) or (
        int(dt.month) == 7 and int(dt.day) <= 22
    ):
        zodiac_sign = "cancer"
    elif (int(dt.month) == 7 and int(dt.day) >= 23) or (
        int(dt.month) == 8 and int(dt.day) <= 22
    ):
        zodiac_sign = "leo"
    elif (int(dt.month) == 8 and int(dt.day) >= 23) or (
        int(dt.month) == 9 and int(dt.day) <= 22
    ):
        zodiac_sign = "virgo"
    elif (int(dt.month) == 9 and int(dt.day) >= 23) or (
        int(dt.month) == 10 and int(dt.day) <= 22
    ):
        zodiac_sign = "libra"
    elif (int(dt.month) == 10 and int(dt.day) >= 23) or (
        int(dt.month) == 11 and int(dt.day) <= 21
    ):
        zodiac_sign = "scorpio"
    elif (int(dt.month) == 11 and int(dt.day) >= 22) or (
        int(dt.month) == 12 and int(dt.day) <= 21
    ):
        zodiac_sign = "sagittarius"

    return zodiac_sign


_VALID_SIGNS = [
    "capricorn",
    "aquarius",
    "pisces",
    "aries",
    "taurus",
    "gemini",
    "cancer",
    "leo",
    "virgo",
    "libra",
    "scorpio",
    "sagittarius",
]
# Some of the constellation names map to different astrological "sign names".
# Astrologers really needs to talk to the IAU...
_CONST_TO_SIGNS = {"capricornus": "capricorn", "scorpius": "scorpio"}

_ZODIAC = (
    (1900, "rat"),
    (1901, "ox"),
    (1902, "tiger"),
    (1903, "rabbit"),
    (1904, "dragon"),
    (1905, "snake"),
    (1906, "horse"),
    (1907, "goat"),
    (1908, "monkey"),
    (1909, "rooster"),
    (1910, "dog"),
    (1911, "pig"),
)


# https://stackoverflow.com/questions/12791871/chinese-zodiac-python-program
def _get_zodiac(yr):
    return _ZODIAC[(yr - _ZODIAC[0][0]) % 12][1]


def horoscope(birthday, corrected=True, chinese=False):
    """
    Enter your birthday as an `astropy.time.Time` object and
    receive a mystical horoscope about things to come.

    Parameters
    ----------
    birthday : `astropy.time.Time` or str
        Your birthday as a `datetime.datetime` or `astropy.time.Time` object
        or "YYYY-MM-DD"string.
    corrected : bool
        Whether to account for the precession of the Earth instead of using the
        ancient Greek dates for the signs.  After all, you do want your *real*
        horoscope, not a cheap inaccurate approximation, right?

    chinese : bool
        Chinese annual zodiac wisdom instead of Western one.

    Returns
    -------
    Infinite wisdom, condensed into astrologically precise prose.

    Notes
    -----
    This function was implemented on April 1.  Take note of that date.
    """
    from bs4 import BeautifulSoup

    today = datetime.now()
    err_msg = "Invalid response from celestial gods (failed to load horoscope)."
    headers = {"User-Agent": "foo/bar"}

    special_words = {
        "([sS]tar[s^ ]*)": "yellow",
        "([yY]ou[^ ]*)": "magenta",
        "([pP]lay[^ ]*)": "blue",
        "([hH]eart)": "red",
        "([fF]ate)": "lightgreen",
    }

    if isinstance(birthday, str):
        birthday = datetime.strptime(birthday, "%Y-%m-%d")

    if chinese:
        # TODO: Make this more accurate by using the actual date, not just year
        # Might need third-party tool like https://pypi.org/project/lunardate
        zodiac_sign = _get_zodiac(birthday.year)
        url = (
            "https://www.horoscope.com/us/horoscopes/yearly/"
            f"{today.year}-chinese-horoscope-{zodiac_sign}.aspx"
        )
        summ_title_sfx = f"in {today.year}"

        try:
            res = Request(url, headers=headers)
            with urlopen(res) as f:
                try:
                    doc = BeautifulSoup(f, "html.parser")
                    # TODO: Also include Love, Family & Friends, Work, Money, More?
                    item = doc.find(id="overview")
                    desc = item.getText()
                except Exception:
                    raise CelestialError(err_msg)
        except Exception:
            raise CelestialError(err_msg)

    else:
        birthday = atime.Time(birthday)

        if corrected:
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")  # Ignore ErfaWarning
                zodiac_sign = get_sun(birthday).get_constellation().lower()
            zodiac_sign = _CONST_TO_SIGNS.get(zodiac_sign, zodiac_sign)
            if zodiac_sign not in _VALID_SIGNS:
                raise HumanError(
                    f"On your birthday the sun was in {zodiac_sign.title()}, which is"
                    " not a sign of the zodiac.  You must not exist.  Or maybe you can"
                    " settle for corrected=False."
                )
        else:
            zodiac_sign = get_sign(birthday.to_datetime())
        url = f"https://astrology.com/horoscope/daily/{zodiac_sign}.html"
        summ_title_sfx = f"on {today.strftime('%Y-%m-%d')}"

        res = Request(url, headers=headers)
        with urlopen(res) as f:
            try:
                doc = BeautifulSoup(f, "html.parser")
                item = doc.find("div", {"id": "content"})
                desc = item.getText()
            except Exception:
                raise CelestialError(err_msg)

    print("*" * 79)
    color_print(f"Horoscope for {zodiac_sign.capitalize()} {summ_title_sfx}:", "green")
    print("*" * 79)
    for block in textwrap.wrap(desc, 79):
        split_block = block.split()
        for i, word in enumerate(split_block):
            for re_word, color in special_words.items():
                match = re.search(re_word, word)
                if match is None:
                    continue
                split_block[i] = _color_text(match.groups()[0], color)
        print(" ".join(split_block))


def inject_horoscope():
    import astropy

    astropy._yourfuture = horoscope


inject_horoscope()
